"""
元数据管理器

管理备份元数据、版本信息、完整性校验等功能。
"""

import json
import hashlib
from pathlib import Path
from typing import Dict, List, Optional, Any, Union
from dataclasses import dataclass, asdict
from datetime import datetime, timezone

from ..utils.logger import get_module_logger
from ..utils.file_utils import FileManager
from ..utils.exceptions import BackupError, create_error_from_exception

logger = get_module_logger(__name__)


@dataclass
class BackupVersion:
    """备份版本信息"""
    version: str
    created_at: datetime
    backup_id: str
    source_index: str
    strategy_type: str
    document_count: int
    size_bytes: int
    checksum: str


@dataclass
class MetadataIndex:
    """元数据索引"""
    backup_id: str
    backup_path: str
    created_at: datetime
    last_modified: datetime
    status: str
    version: str
    tags: List[str]
    description: Optional[str] = None


class MetadataManager:
    """元数据管理器
    
    负责管理备份的元数据信息，包括版本控制、完整性校验等。
    """
    
    def __init__(self, metadata_root: Optional[Union[str, Path]] = None):
        """初始化元数据管理器
        
        Args:
            metadata_root: 元数据根目录
        """
        self.metadata_root = Path(metadata_root) if metadata_root else Path.cwd() / ".es-archive"
        self.file_manager = FileManager()
        
        # 确保元数据目录存在
        self.metadata_root.mkdir(parents=True, exist_ok=True)
        
        # 元数据文件路径
        self.index_file = self.metadata_root / "backup_index.json"
        self.versions_file = self.metadata_root / "versions.json"
        
        logger.info(f"元数据管理器初始化，根目录: {self.metadata_root}")
    
    def register_backup(
        self,
        backup_metadata: Dict[str, Any],
        tags: Optional[List[str]] = None,
        description: Optional[str] = None
    ) -> None:
        """注册备份到元数据索引
        
        Args:
            backup_metadata: 备份元数据
            tags: 标签列表
            description: 描述信息
        """
        backup_id = backup_metadata["backup_id"]
        logger.info(f"注册备份到元数据索引: {backup_id}")
        
        try:
            # 创建元数据索引条目
            index_entry = MetadataIndex(
                backup_id=backup_id,
                backup_path=backup_metadata["backup_path"],
                created_at=datetime.fromisoformat(backup_metadata["start_time"]),
                last_modified=datetime.now(timezone.utc),
                status=backup_metadata["status"],
                version=self._generate_version(),
                tags=tags or [],
                description=description
            )
            
            # 更新索引
            self._update_backup_index(index_entry)
            
            # 创建版本记录
            version_info = BackupVersion(
                version=index_entry.version,
                created_at=index_entry.created_at,
                backup_id=backup_id,
                source_index=backup_metadata["source_index"],
                strategy_type=backup_metadata.get("strategy", {}).get("strategy_type", "unknown"),
                document_count=backup_metadata.get("backed_up_documents", 0),
                size_bytes=backup_metadata.get("compressed_size_bytes", 0),
                checksum=self._calculate_backup_checksum(backup_metadata["backup_path"])
            )
            
            self._update_version_history(version_info)
            
            # 保存完整元数据
            self._save_backup_metadata(backup_metadata)
            
            logger.info(f"备份注册完成: {backup_id}, 版本: {index_entry.version}")
            
        except Exception as e:
            logger.error(f"注册备份失败: {backup_id}, {e}")
            raise BackupError(f"注册备份失败: {backup_id}", backup_id, e)
    
    def get_backup_info(self, backup_id: str) -> Optional[Dict[str, Any]]:
        """获取备份信息
        
        Args:
            backup_id: 备份ID
            
        Returns:
            备份信息字典
        """
        try:
            # 从索引获取基本信息
            index = self._load_backup_index()
            index_entry = None
            
            for entry in index:
                if entry["backup_id"] == backup_id:
                    index_entry = entry
                    break
            
            if not index_entry:
                return None
            
            # 加载完整元数据
            metadata_file = self.metadata_root / "backups" / f"{backup_id}.json"
            if metadata_file.exists():
                with open(metadata_file, 'r', encoding='utf-8') as f:
                    full_metadata = json.load(f)
                
                # 合并信息
                return {
                    **index_entry,
                    "full_metadata": full_metadata
                }
            
            return index_entry
            
        except Exception as e:
            logger.error(f"获取备份信息失败: {backup_id}, {e}")
            return None
    
    def list_backups(
        self,
        tags: Optional[List[str]] = None,
        status: Optional[str] = None,
        source_index: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """列出备份
        
        Args:
            tags: 标签过滤
            status: 状态过滤
            source_index: 源索引过滤
            
        Returns:
            备份列表
        """
        try:
            index = self._load_backup_index()
            filtered_backups = []
            
            for entry in index:
                # 应用过滤条件
                if tags and not any(tag in entry.get("tags", []) for tag in tags):
                    continue
                
                if status and entry.get("status") != status:
                    continue
                
                if source_index:
                    # 需要加载完整元数据来检查源索引
                    full_info = self.get_backup_info(entry["backup_id"])
                    if (not full_info or 
                        full_info.get("full_metadata", {}).get("source_index") != source_index):
                        continue
                
                filtered_backups.append(entry)
            
            # 按创建时间排序
            filtered_backups.sort(key=lambda x: x["created_at"], reverse=True)
            return filtered_backups
            
        except Exception as e:
            logger.error(f"列出备份失败: {e}")
            return []
    
    def delete_backup_metadata(self, backup_id: str) -> bool:
        """删除备份元数据
        
        Args:
            backup_id: 备份ID
            
        Returns:
            是否删除成功
        """
        try:
            logger.info(f"删除备份元数据: {backup_id}")
            
            # 从索引中移除
            index = self._load_backup_index()
            updated_index = [entry for entry in index if entry["backup_id"] != backup_id]
            
            if len(updated_index) == len(index):
                logger.warning(f"备份不存在于索引中: {backup_id}")
                return False
            
            self._save_backup_index(updated_index)
            
            # 删除完整元数据文件
            metadata_file = self.metadata_root / "backups" / f"{backup_id}.json"
            if metadata_file.exists():
                metadata_file.unlink()
            
            # 从版本历史中移除
            self._remove_from_version_history(backup_id)
            
            logger.info(f"备份元数据删除完成: {backup_id}")
            return True
            
        except Exception as e:
            logger.error(f"删除备份元数据失败: {backup_id}, {e}")
            return False
    
    def verify_backup_integrity(self, backup_id: str, backup_path: Optional[str] = None) -> Dict[str, Any]:
        """验证备份完整性

        Args:
            backup_id: 备份ID
            backup_path: 备份路径（可选，用于验证未注册的备份）

        Returns:
            验证结果
        """
        logger.info(f"验证备份完整性: {backup_id}")

        result = {
            "backup_id": backup_id,
            "valid": False,
            "issues": [],
            "checksum_match": False,
            "files_exist": False,
            "metadata_valid": False
        }

        try:
            # 获取备份信息
            backup_info = self.get_backup_info(backup_id)

            # 如果在元数据索引中找不到，尝试直接验证备份路径
            if not backup_info and backup_path:
                logger.info(f"备份未在元数据索引中注册，直接验证备份路径: {backup_path}")
                return self._verify_backup_by_path(backup_id, backup_path)
            elif not backup_info:
                result["issues"].append("备份不存在于元数据索引中")
                return result
            
            backup_path = backup_info["backup_path"]
            full_metadata = backup_info.get("full_metadata", {})
            
            # 检查备份路径是否存在
            if not Path(backup_path).exists():
                result["issues"].append(f"备份路径不存在: {backup_path}")
                return result
            
            result["files_exist"] = True
            
            # 验证元数据文件
            metadata_file = Path(backup_path) / "metadata.json"
            if not metadata_file.exists():
                result["issues"].append("备份元数据文件不存在")
            else:
                result["metadata_valid"] = True
            
            # 验证分片文件
            chunks = full_metadata.get("chunks", [])
            missing_chunks = []
            
            for chunk_file in chunks:
                if not Path(chunk_file).exists():
                    missing_chunks.append(chunk_file)
            
            if missing_chunks:
                result["issues"].append(f"缺失分片文件: {len(missing_chunks)} 个")
            
            # 验证校验和
            current_checksum = self._calculate_backup_checksum(backup_path)
            version_info = self._get_version_info(backup_id)
            
            if version_info and version_info.get("checksum"):
                if current_checksum == version_info["checksum"]:
                    result["checksum_match"] = True
                else:
                    result["issues"].append("校验和不匹配，备份可能已损坏")
            
            # 综合判断
            result["valid"] = (
                result["files_exist"] and 
                result["metadata_valid"] and 
                not missing_chunks and
                (result["checksum_match"] or not version_info.get("checksum"))
            )
            
            if result["valid"]:
                logger.info(f"备份完整性验证通过: {backup_id}")
            else:
                logger.warning(f"备份完整性验证失败: {backup_id}, 问题: {result['issues']}")
            
        except Exception as e:
            logger.error(f"备份完整性验证出错: {backup_id}, {e}")
            result["issues"].append(f"验证过程出错: {e}")
        
        return result

    def _verify_backup_by_path(self, backup_id: str, backup_path: str) -> Dict[str, Any]:
        """通过路径直接验证备份（用于未注册的备份）

        Args:
            backup_id: 备份ID
            backup_path: 备份路径

        Returns:
            验证结果
        """
        result = {
            "backup_id": backup_id,
            "valid": False,
            "issues": [],
            "checksum_match": False,
            "files_exist": False,
            "metadata_valid": False
        }

        try:
            backup_path_obj = Path(backup_path)

            # 检查备份路径是否存在
            if not backup_path_obj.exists():
                result["issues"].append(f"备份路径不存在: {backup_path}")
                return result

            result["files_exist"] = True

            # 验证元数据文件
            metadata_file = backup_path_obj / "metadata.json"
            if not metadata_file.exists():
                result["issues"].append("备份元数据文件不存在")
                return result

            result["metadata_valid"] = True

            # 读取元数据
            with open(metadata_file, 'r', encoding='utf-8') as f:
                metadata = json.load(f)

            # 验证数据文件
            chunk_files = list(backup_path_obj.glob('chunk_*.json.gz'))
            if not chunk_files:
                result["issues"].append("没有找到数据文件")
                return result

            # 验证索引元数据
            index_metadata_file = backup_path_obj / "index_metadata.json"
            if not index_metadata_file.exists():
                result["issues"].append("索引元数据文件不存在")

            # 基本验证通过
            result["valid"] = True
            result["checksum_match"] = True  # 对于直接验证，跳过校验和检查

            logger.info(f"直接路径验证通过: {backup_id}")

        except Exception as e:
            logger.error(f"直接路径验证失败: {backup_id}, {e}")
            result["issues"].append(f"验证过程出错: {e}")

        return result

    def get_backup_statistics(self) -> Dict[str, Any]:
        """获取备份统计信息
        
        Returns:
            统计信息
        """
        try:
            index = self._load_backup_index()
            versions = self._load_version_history()
            
            # 基本统计
            total_backups = len(index)
            status_counts = {}
            strategy_counts = {}
            total_size = 0
            
            for entry in index:
                status = entry.get("status", "unknown")
                status_counts[status] = status_counts.get(status, 0) + 1
                
                # 从版本信息获取更多统计
                version_info = self._get_version_info(entry["backup_id"])
                if version_info:
                    strategy = version_info.get("strategy_type", "unknown")
                    strategy_counts[strategy] = strategy_counts.get(strategy, 0) + 1
                    total_size += version_info.get("size_bytes", 0)
            
            # 时间统计
            if index:
                creation_times = [datetime.fromisoformat(entry["created_at"]) for entry in index]
                oldest_backup = min(creation_times)
                newest_backup = max(creation_times)
            else:
                oldest_backup = newest_backup = None
            
            return {
                "total_backups": total_backups,
                "status_distribution": status_counts,
                "strategy_distribution": strategy_counts,
                "total_size_bytes": total_size,
                "total_size_formatted": self.file_manager.format_size(total_size),
                "oldest_backup": oldest_backup.isoformat() if oldest_backup else None,
                "newest_backup": newest_backup.isoformat() if newest_backup else None,
                "total_versions": len(versions)
            }
            
        except Exception as e:
            logger.error(f"获取备份统计信息失败: {e}")
            return {}
    
    def _update_backup_index(self, index_entry: MetadataIndex) -> None:
        """更新备份索引"""
        index = self._load_backup_index()
        
        # 查找是否已存在
        updated = False
        for i, entry in enumerate(index):
            if entry["backup_id"] == index_entry.backup_id:
                index[i] = asdict(index_entry)
                updated = True
                break
        
        if not updated:
            index.append(asdict(index_entry))
        
        self._save_backup_index(index)
    
    def _load_backup_index(self) -> List[Dict[str, Any]]:
        """加载备份索引"""
        if self.index_file.exists():
            try:
                with open(self.index_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                logger.warning(f"加载备份索引失败: {e}")
        
        return []
    
    def _save_backup_index(self, index: List[Dict[str, Any]]) -> None:
        """保存备份索引"""
        # 转换datetime对象为字符串
        for entry in index:
            for key, value in entry.items():
                if isinstance(value, datetime):
                    entry[key] = value.isoformat()
        
        with open(self.index_file, 'w', encoding='utf-8') as f:
            json.dump(index, f, indent=2, ensure_ascii=False, default=str)
    
    def _update_version_history(self, version_info: BackupVersion) -> None:
        """更新版本历史"""
        versions = self._load_version_history()
        versions.append(asdict(version_info))
        self._save_version_history(versions)
    
    def _load_version_history(self) -> List[Dict[str, Any]]:
        """加载版本历史"""
        if self.versions_file.exists():
            try:
                with open(self.versions_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                logger.warning(f"加载版本历史失败: {e}")
        
        return []
    
    def _save_version_history(self, versions: List[Dict[str, Any]]) -> None:
        """保存版本历史"""
        # 转换datetime对象为字符串
        for version in versions:
            for key, value in version.items():
                if isinstance(value, datetime):
                    version[key] = value.isoformat()
        
        with open(self.versions_file, 'w', encoding='utf-8') as f:
            json.dump(versions, f, indent=2, ensure_ascii=False, default=str)
    
    def _save_backup_metadata(self, metadata: Dict[str, Any]) -> None:
        """保存完整备份元数据"""
        backup_id = metadata["backup_id"]
        backups_dir = self.metadata_root / "backups"
        backups_dir.mkdir(exist_ok=True)
        
        metadata_file = backups_dir / f"{backup_id}.json"
        with open(metadata_file, 'w', encoding='utf-8') as f:
            json.dump(metadata, f, indent=2, ensure_ascii=False, default=str)
    
    def _generate_version(self) -> str:
        """生成版本号"""
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
        return f"v{timestamp}"
    
    def _calculate_backup_checksum(self, backup_path: Union[str, Path]) -> str:
        """计算备份校验和"""
        backup_path = Path(backup_path)
        
        # 收集所有文件的校验和
        file_checksums = []
        
        for file_path in sorted(backup_path.rglob("*")):
            if file_path.is_file():
                with open(file_path, 'rb') as f:
                    file_hash = hashlib.sha256(f.read()).hexdigest()
                    file_checksums.append(f"{file_path.name}:{file_hash}")
        
        # 计算总校验和
        combined = "|".join(file_checksums)
        return hashlib.sha256(combined.encode()).hexdigest()
    
    def _get_version_info(self, backup_id: str) -> Optional[Dict[str, Any]]:
        """获取版本信息"""
        versions = self._load_version_history()
        
        for version in versions:
            if version["backup_id"] == backup_id:
                return version
        
        return None
    
    def _remove_from_version_history(self, backup_id: str) -> None:
        """从版本历史中移除"""
        versions = self._load_version_history()
        updated_versions = [v for v in versions if v["backup_id"] != backup_id]
        self._save_version_history(updated_versions)
